﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;
using APC.Common;

namespace APC
{
    class Order
    {

        #region Delcarations

        DataHandlingDataContext ctx;

        #endregion

        #region Enum

        public enum eActionType { TypeSave, TypeEdit, TypeDelete }

        /// <summary>
        /// Flag for readiness for Production
        /// </summary>
        public enum OrderState 
        {
            NotReady = 0,
            ReadyForProduction = 1,
            InProduction = 2
        }

        #endregion

        #region Properties

        public int Id
        {
            get { return _Id; }
            set { _Id = value; }
        }
        private int _Id;

        public string Name
        {
            get { return this._Name; }
            set { this._Name = value; }
        }
        private string _Name;

        public string RecipeName
        {
            get { return this._RecipeName; }
            set { this._RecipeName = value; }
        }
        private string _RecipeName;

        public string RecipeNumber
        {
            get { return this._RecipeNumber; }
            set { this._RecipeNumber = value; }
        }
        private string _RecipeNumber;

        /// <summary>
        /// Calculated status of running order = number of batches * each batch status
        /// </summary>
        public Int32 OrderCalcultatedStatus
        {
            get { return this._OrderCalcultatedStatus; }
            set { this._OrderCalcultatedStatus = value; }
        }
        private Int32 _OrderCalcultatedStatus;

        /// <summary>
        /// Total number of batches in order
        /// </summary>
        public Int32 OrderBatchTotal
        {
            get { return this._OrderBatchTotal; }
            set { this._OrderBatchTotal = value; }
        }
        private Int32 _OrderBatchTotal;

        public Int32 RecipeId
        {
            get { return this._RecipeId; }
            set { this._RecipeId = value; }
        }
        private Int32 _RecipeId;

        public string OrderNumber
        {
            get { return this._OrderNumber; }
            set { this._OrderNumber = value; }
        }
        private string _OrderNumber;

        public string OrderPostfixNumber
        {
            get { return this._OrderPostfixNumber; }
            set { this._OrderPostfixNumber = value; }
        }
        private string _OrderPostfixNumber;

        public Int32 EndStoreLocation
        {
            get { return this._EndStoreLocation; }
            set { this._EndStoreLocation = value; }
        }
        private Int32 _EndStoreLocation;

        public Int32 OrderQuantum
        {
            get { return this._OrderQuantum; }
            set { this._OrderQuantum = value; }
        }
        private Int32 _OrderQuantum;

        public Int32 RecommendedBatchSize
        {
            get { return this._RecommendedBatchSize; }
            set { this._RecommendedBatchSize = value; }
        }
        private Int32 _RecommendedBatchSize;

        public Int32 OrderStatus
        {
            get { return this._OrderStatus; }
            set { this._OrderStatus = value; }
        }
        private Int32 _OrderStatus;

        public APC.Common.Recipe Recipe
        {
            set { _Recipe = value; }
            get { return _Recipe; }
        }
        private APC.Common.Recipe _Recipe;

        public List<APC.RawMaterials> IngredientList
        {
            set { this._IngredientList = value; }
            get { return this._IngredientList; }
        }
        private List<APC.RawMaterials> _IngredientList;

        public List<APC.Common.Order> OrderList
        {
            set { this._OrderList = value; }
            get { return this._OrderList; }
        }
        private List<APC.Common.Order> _OrderList;

        public bool IsInProduction
        {
            set { _IsInProduction = value; }
            get { return _IsInProduction; }
        }
        private bool _IsInProduction = false;

        public DateTime StartDate
        {
            set { _StartDate = value; }
            get { return _StartDate; }
        }
        private DateTime _StartDate;

        /// <summary>
        /// Unique sequence number assigned as order is send to production
        /// </summary>
        public int OrderSequence
        {
            set { _OrderSequence = value; }
            get { return _OrderSequence; }
        }
        private int _OrderSequence;

        /// <summary>
        /// Return number of batches in selected order
        /// </summary>
        public decimal NumberOfBatches
        {
            set { _NumerOfBatches = value; }
            get { return _NumerOfBatches; }
        }
        private decimal _NumerOfBatches;


        /// <summary>
        /// Returns list of orders pending or in process in plc system
        /// </summary>
        public List<APC.Common.Order> ActiveOrderList
        {
            set { this._ActiveOrderList = value; }
            get { return this._ActiveOrderList; }
        }
        private List<APC.Common.Order> _ActiveOrderList;

        /// <summary>
        /// Returns list of pending orders
        /// </summary>
        public List<APC.Common.Order> PendingOrderList
        {
            set { this._PendingOrderList = value; }
            get { return this._PendingOrderList; }
        }
        private List<APC.Common.Order> _PendingOrderList;

        /// <summary>
        /// Returns list of all Storage locations for selected order
        /// </summary>
        public List<APC.Common.Location> StorageLocationList
        {
            set { this._StorageLocationList = value; }
            get { return this._StorageLocationList; }
        }
        private List<APC.Common.Location> _StorageLocationList;


        /// <summary>
        /// Enum: TypeSave, TypeUpdate, TypeDelete
        /// </summary>
        public eActionType ActionType
        {
            get { return _ActionType; }
            set { this._ActionType = value; }
        }
        private eActionType _ActionType;

        #endregion

        #region Constructors

        public Order(Int32 Id)
        {
            this._Id = Id;
            LoadOrder();
        }

        public Order()
        {
        }

        public Order(bool LoadLists)
        {
            ctx = new DataHandlingDataContext(new Data().ConnectionString);

            LoadOrderHistoryList();
            LoadActiveOrderList();
            LoadPendingOrderList();
        }

        #endregion

        #region Methods

        private void LoadOrder()
        {
            ctx = new DataHandlingDataContext(new Data().ConnectionString);

            using (ctx)
            {
                var query = ctx.Orders.Single(x => x.Id == _Id);

                if (query != null)
                {
                    this._Id = query.Id;
                    this._Name = query.Name.Trim();
                    this._OrderQuantum = Convert.ToInt32(query.Amount);
                    this._OrderNumber = query.OrderNumber.Trim();
                    this._OrderStatus = Convert.ToInt32(query.OrdreStatus);
                    this._OrderSequence = Convert.ToInt32(query.OrderSequence); ;
                    this._RecipeId = Convert.ToInt32(query.RecipeId);
                    this._EndStoreLocation = Convert.ToInt32(query.EndStoredLocation);
                    this._RecipeName = query.Recipe.RecipeName;
                    this._RecipeNumber = query.Recipe.RecipeNumber;
                    this._RecommendedBatchSize = Convert.ToInt32(query.RecBatchSize);
                    this._Recipe = query.Recipe;
                    this._NumerOfBatches = Decimal.Divide(_OrderQuantum,_RecommendedBatchSize);
                    
                    if (query.StartDate != null)
                    {
                        this._StartDate = (DateTime)query.StartDate;
                    }

                    if (query.OrderState == 2) //Order can not be edited
                    {
                        this._IsInProduction = true;
                    }
                    else
                    {
                        this._IsInProduction = false;
                    }

                    //Loop StorageSiloArray and load locations
                    string[] SiloArray = query.StorageSiloArray.Split(',');

                    if (SiloArray.Length > 0)
                    {
                        if (SiloArray[0] != "")
                        {
                            _StorageLocationList = new List<APC.Common.Location>();
                            foreach (string s in SiloArray)
                            {
                                if (s != "")
                                {
                                    var storage = ctx.Locations.Single(x => x.Id == Convert.ToInt32(s));
                                    _StorageLocationList.Add(storage);
                                }
                            }
                        }
                    }

                    //Load the stored ingridients with its location at it was set at production time
                    var queryI = (from po in ctx.ProductionOrders
                                  from l in ctx.Locations
                                  from t in ctx.TagListGroups
                                  where po.OrderId == _Id && po.LocationId == l.Id && l.TagListeGroupId == t.Id
                                  orderby t.Id
                                  select po).Select(x => new RawMaterials()
                                  {
                                      AmountProcent = Convert.ToDecimal(x.AmountProcent),
                                      Tolerance = Convert.ToDecimal(x.ToleranceProcent),
                                      Name = x.RawMaterialName,
                                      Number = x.RawMaterialNumber,
                                      LocationName = x.LocationName,
                                      Id = Convert.ToInt32(x.RawMaterialId),
                                      LocationId = Convert.ToInt32(x.LocationId)
                                  }).ToList();

                    this._IngredientList = queryI;

                    //Count number of batches in order
                    this._OrderBatchTotal = (int)(from b in ctx.BatchListes
                                                  where b.OrderId == _Id
                                                  group b by new { b.BatchCountNo, b.Status }
                                                      into grp
                                                      select new
                                                      {
                                                          Id = grp.Key.BatchCountNo,
                                                          Status = grp.Key.Status
                                                      }).Count();

                    //Calculate batch status for selected order
                    this._OrderCalcultatedStatus = (int)(from b in ctx.BatchListes
                                                         where b.OrderId == _Id
                                                         group b by new { b.BatchCountNo, b.Status }
                                                             into grp
                                                             select new
                                                             {
                                                                 Status = grp.Key.Status
                                                             }).Sum(x => x.Status);

                }
            }
        }

        public OrderContaminationInfo CheckContamination(int RawMaterialId, int BatchFlush)
        {

            ctx = new DataHandlingDataContext(new Data().ConnectionString);

            int OrderAmount = 0;
            int RecBachtSize = 0;
            decimal NumberOfBatches = 0;
            bool ContaminationRisk = false;
            OrderContaminationInfo coi = null;

            //Return 5 latest orders prior to this order
            var order = (from o in ctx.Orders
                         orderby o.OrderSequence descending
                         select o).Take(10).ToList();

            //Calculate number of batches until reach of BatchFlush value (e.g. BatchFlush for an ingridient could be set to 24 batches, 
            //then check orders until at total number of batches reach 24)
            foreach (APC.Common.Order or in order)
            {
                if (ContaminationRisk)
                {
                    break;
                }

                OrderAmount = Convert.ToInt32(or.Amount);
                RecBachtSize = Convert.ToInt32(or.RecBatchSize);

                NumberOfBatches += Decimal.Divide(OrderAmount, RecBachtSize);

                if (NumberOfBatches > BatchFlush)
                {
                    break;
                }
                else
                {
                    foreach (ProductionOrder po in or.ProductionOrders)
                    {
                        if (RawMaterialId == po.RawMaterialId)
                        {
                            coi = new OrderContaminationInfo();
                            coi.OrderId = or.Id;
                            coi.OrderName = or.Name.Trim();
                            coi.OrderNumber = or.OrderNumber.Trim();
                            coi.RawMaterialId = RawMaterialId;
                            coi.RawMaterialName = po.RawMaterialName.Trim();
                            coi.RawMaterialNumber = po.RawMaterialNumber.Trim();

                            ContaminationRisk = true;
                            break;
                        }
                    }
                }

            }

            return coi;
        }

        private void LoadOrderHistoryList()
        {
            this._OrderList = ctx.Orders.OrderByDescending(x => x.OrderSequence).Where(x => x.StartDate != null && x.EndDate != null).ToList<APC.Common.Order>();
        }

        /// <summary>
        /// Loads all orders where the batches are in Zenon/PLC system
        /// </summary>
        /// <returns></returns>
        public List<Order> GetOrdersInProcess()
        {
            ctx = new DataHandlingDataContext(new Data().ConnectionString);
            List<Order> OrderList = new List<Order>();

            using (ctx)
            {
                //Get orders where one or more batches have a status different form 0 = in progress
                var batchinprocess = (from ba in ctx.BatchListes
                                      where ba.Status > 0
                                      group ba by new { ba.OrderId, ba.OrderSequence }
                                          into grp
                                          select new
                                          {
                                              OrderSequence = grp.Key.OrderSequence,
                                              OrderId = grp.Key.OrderId
                                          });

                //Load the orders into list 
                foreach (var o in batchinprocess.OrderByDescending(x => x.OrderSequence))
                {
                    Order order = new Order(o.OrderId);
                    OrderList.Add(order);
                }

                return OrderList;
            }
        }

        /// <summary>
        /// Loads all orders currently loaded into batchlist
        /// </summary>
        private void LoadActiveOrderList()
        {
            this._ActiveOrderList = ctx.Orders.Where(x => x.EndDate == null).OrderByDescending(x => x.OrderSequence).ToList<APC.Common.Order>();
            //this._ActiveOrderList = ctx.Orders.Where(x => x.OrdreStatus < 2).OrderBy(x => x.OrdreStatus).ToList<APC.Common.Order>();
        }

 
        private void LoadPendingOrderList()
        {
            this._PendingOrderList = ctx.Orders.Where(x => x.StartDate == null).OrderByDescending(x => x.OrderSequence).ToList<APC.Common.Order>();

           
           
        }

        /// <summary>
        /// Toggles order state for all pending orders
        /// </summary>
        /// <param name="OrderState"></param>
        public void SetOrderStateAll(OrderState OrderState)
        {
            ctx = new DataHandlingDataContext(new Data().ConnectionString);

            switch (OrderState)
            {
                case Order.OrderState.NotReady:
                    ctx.ExecuteCommand("UPDATE [Order] SET OrderState = 0 WHERE StartDate is null");
                    break;

                case Order.OrderState.ReadyForProduction:
                    ctx.ExecuteCommand("UPDATE [Order] SET OrderState = 1 WHERE StartDate is null");
                    break;
            }
        }
     
        /// <summary>
        /// Toggles Order status: if not ready then Brdige can not initiate production
        /// </summary>
        /// <param name="IsLocked"></param>
        public void SetOrderState(OrderState OrderState)
        {
            ctx = new DataHandlingDataContext(new Data().ConnectionString);

            using (ctx)
            {
                var o = ctx.Orders.Single(x => x.Id == _Id);
                if (o != null)
                {
                    o.OrderState = (int)OrderState;
                }
                ctx.SubmitChanges();
            }
        }

        /// <summary>
        /// Saves the new order postfix number to database
        /// </summary>
        /// <param name="PostfixNumber"></param>
        public void SaveOrderPostfixNumber(string PostfixNumber)
        {
            ctx = new DataHandlingDataContext(new Data().ConnectionString);

            using (ctx)
            {
                var opn = ctx.ApplicationSetups.Single(x => x.TagName == "OrderPostfixNumber");
                if (opn != null)
                {
                    opn.Value = PostfixNumber;
                }
                ctx.SubmitChanges();
            }
        }

        //Returns order number withe datestamp + postfix number counted up with 1
        public string GetNextOrderNumber()
        {
            string OrderNumber = "";
            string pn = "";
            int temp = 0;
            DateTime dt = DateTime.Today;
            string dd = "";
            string mm = "";
            string yyyy = "";

            ctx = new DataHandlingDataContext(new Data().ConnectionString);

            using (ctx)
            {
                var opn = ctx.ApplicationSetups.Single(x => x.TagName == "OrderPostfixNumber");

                if (opn != null)
                {
                    pn = opn.Value;
                    temp = Convert.ToInt32(pn);

                    pn = (temp + 1).ToString("0000");
                    this._OrderPostfixNumber = pn;

                    dd = dt.Day.ToString("00");
                    mm = dt.Month.ToString("00");
                    yyyy = dt.Year.ToString();

                    OrderNumber = yyyy + "-" + mm + "-" + dd + "-" + pn;
                }
            }

            return OrderNumber;
        }

        /// <summary>
        /// Contamination check - previous orders ingridients must be evaluated against negative list
        /// </summary>
        /// <returns></returns>
        public void LoadLastOrderIngridients()
        {
            ctx = new DataHandlingDataContext(new Data().ConnectionString);

            int OrderAmount = 0;
            int RecBachtSize = 0;
            decimal NumberOfBatches = 0;

            //Return 5 latest orders prior to this order
            var order = (from o in ctx.Orders
                         orderby o.OrderSequence descending
                         select o).Take(5).ToList();

            //Calculate number of batches until reach of BatchFlush value (e.g. BatchFlush for an ingridient colud be set 24 batches, the check orders until at total number of batches reach 24)
            foreach (APC.Common.Order or in order)
            {
                OrderAmount = Convert.ToInt32(or.Amount);
                RecBachtSize = Convert.ToInt32(or.RecBatchSize);

                NumberOfBatches += Decimal.Divide(OrderAmount, RecBachtSize);


            }


            //int? order = ctx.Orders.Max(x => x.OrderSequence);

            if (order != null)
            {
                ////From latest OrderSequense fins the Order.Id
                //Common.Order o = ctx.Orders.FirstOrDefault(x => x.OrderSequence == order);

                //if (o != null)
                //{  
                //    //Load properties of latest Order
                //    this._Id = o.Id;
                //    LoadOrder();
                //}    
            }  
        }

        /// <summary>
        /// Returns OrderSequence to be counted up with 1 for new order
        /// </summary>
        /// <returns></returns>
        public int GetOrderSequence()
        {
            ctx = new DataHandlingDataContext(new Data().ConnectionString);

            int _OrderSequence = 0;

            var order = ctx.Orders.Max(x => x.OrderSequence);

            if (order != null)
            {
                _OrderSequence = order.Value + 1;
            }

            return _OrderSequence;
        }

        /// <summary>
        /// Deletes selected order
        /// </summary>
        public void Delete()
        {
            ctx = new DataHandlingDataContext(new Data().ConnectionString);

            using (ctx)
            {
                APC.Common.Order o;

                o = ctx.Orders.Single(u => u.Id == _Id);
                o.Id = _Id;
                ctx.Orders.DeleteOnSubmit(o);
                ctx.SubmitChanges();
            }
        }

        public int Save()
        {
            _OrderSequence = GetOrderSequence();
            try
            {
                ctx = new DataHandlingDataContext(new Data().ConnectionString);

                using (ctx)
                {
                    APC.Common.Order o;
                    APC.Common.ProductionOrder po;

                    string StorageArrayString = "";
                    foreach (APC.Common.Location l in _StorageLocationList) //Stores endstorage silo numbers as string e.g. "3,5,8". First priority is storage silo 3, then silo 5, etc.
                    {
                        StorageArrayString += l.Id + ",";
                    }

                    switch (this._ActionType)
                    {
                        case eActionType.TypeSave:
                            o = new APC.Common.Order();
                            o.Name = _Name;
                            o.OrderNumber = _OrderNumber;
                            o.OrdreStatus = _OrderStatus;
                            o.RecipeId = _RecipeId;
                            o.Amount = _OrderQuantum;
                            o.RecBatchSize = _RecommendedBatchSize;
                            o.Description = "";
                            o.CrearteDate = DateTime.Now;
                            o.OrderSequence = _OrderSequence;
                            o.StorageSiloArray = StorageArrayString;
                            o.OrderState = (int)OrderState.NotReady; //Will be set true when full batchliste is saved to database

                            ctx.Orders.InsertOnSubmit(o);
                            ctx.SubmitChanges(); //Save to database
                            _Id = o.Id; //Post back new Id for order

                            foreach (APC.RawMaterials raw in this._IngredientList)
                            {
                                //Save a snapshot of the orders with ingridients and amount
                                po = new APC.Common.ProductionOrder();
                                po.OrderId = _Id;
                                po.RecipeId = _RecipeId;
                                po.RawMaterialId = raw.Id;
                                po.RawMaterialName = raw.Name;
                                po.RawMaterialNumber = raw.Number;
                                po.LocationId = raw.LocationId;
                                po.LocationName = raw.LocationName;
                                po.TagListGroupId = raw.TagListeGroupId;
                                po.LocationPosition = raw.LocationPosition;
                                po.ToleranceProcent = raw.Tolerance;
                                po.AmountProcent = raw.AmountProcent;
                                po.InsertDateTime = DateTime.Now;
                                ctx.ProductionOrders.InsertOnSubmit(po);
                            }
                            ctx.SubmitChanges(); //Save to database
 

                            break;

                        case eActionType.TypeEdit:
                            o = ctx.Orders.Single(u => u.Id == _Id);
                            o.Id = _Id;
                            o.OrderNumber = _OrderNumber;
                            o.OrdreStatus = _OrderStatus;
                            o.RecipeId = _RecipeId;
                            o.Amount = _OrderQuantum;
                            o.Description = "";
                            ctx.SubmitChanges(); //Save to database
                            break;

                            //Delete pre existing records for this order
                            ctx.ExecuteCommand("DELETE FROM ProductionOrder WHERE OrderId = " + _Id);

                            //Write new set of records
                            foreach (APC.RawMaterials raw in this._IngredientList)
                            {
                                //Save a snapshot of the orders with ingridients and amount
                                po = new APC.Common.ProductionOrder();
                                po.OrderId = _Id;
                                po.RecipeId = _RecipeId;
                                po.RawMaterialId = raw.Id;
                                po.RawMaterialName = raw.Name;
                                po.RawMaterialNumber = raw.Number;
                                po.LocationId = raw.LocationId;
                                po.LocationName = raw.LocationName;
                                po.TagListGroupId = raw.TagListeGroupId;
                                po.LocationPosition = raw.LocationPosition;
                                po.ToleranceProcent = raw.Tolerance;
                                po.AmountProcent = raw.AmountProcent;
                                po.InsertDateTime = DateTime.Now;
                                ctx.ProductionOrders.InsertOnSubmit(po);
                            }
                            ctx.SubmitChanges(); //Save to database

                        case eActionType.TypeDelete:
                            o = ctx.Orders.Single(u => u.Id == _Id);
                            o.Id = _Id;
                            ctx.Orders.DeleteOnSubmit(o);
                            ctx.SubmitChanges(); //Save to database
                            break;

                    }

                    Log log = new Log();
                    log.UserId = "sys";
                    log.ObjectName = "Order";
                    log.ObjectId = _Id.ToString();
                    log.ObjectChangeType = _ActionType.ToString();
                    log.LogMessage = "";
                    log.Write();
                }
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(ex.Message); //Write to log!
            }


            return _Id; //Returns Order Id for newly created order
        }

        /// <summary>
        /// Test method: delete all orders and batches
        /// </summary>
        /// <param name="LocationId"></param>
        public void DeleteOrdersBatches()
        {
            ctx = new DataHandlingDataContext(new Data().ConnectionString);

            ctx.ExecuteCommand("DELETE FROM [Order]");
            ctx.ExecuteCommand("DELETE FROM BatchListe");
        }



        /// <summary>
        /// Test method: delete all orders and batches
        /// </summary>
        /// <param name="LocationId"></param>
        public void MarkOrderFinished(int OrderId)
        {
            int yyyy;
            int mm;
            int dd;
            int hh;
            int mi;

            DateTime d = DateTime.Now;

            yyyy = d.Year;
            mm = d.Month;
            dd = d.Day;
            hh = d.Hour;
            mi = d.Minute;

            string newDate = yyyy.ToString() + "-" + mm.ToString() + "-" + dd.ToString() + " " + hh.ToString() + ":" + mi.ToString();


            ctx = new DataHandlingDataContext(new Data().ConnectionString);

            ctx.ExecuteCommand("UPDATE [Order] SET StartDate = '" + newDate + "', EndDate = '" + newDate + "' WHERE Id = " + OrderId);
            ctx.ExecuteCommand("DELETE FROM BatchListe WHERE OrderId = " + OrderId);
        }

        /// <summary>
        /// Test method: delete all orders and batches
        /// </summary>
        /// <param name="LocationId"></param>
        public void FirstBatchToPLC(int OrderId)
        {
            int yyyy;
            int mm;
            int dd;
            int hh;
            int mi;

            DateTime d = DateTime.Now;

            yyyy = d.Year;
            mm = d.Month;
            dd = d.Day;
            hh = d.Hour;
            mi = d.Minute;

            string newDate = yyyy.ToString() + "-" + mm.ToString() + "-" + dd.ToString() + " " + hh.ToString() + ":" + mi.ToString();


            ctx = new DataHandlingDataContext(new Data().ConnectionString);

            ctx.ExecuteCommand("UPDATE [Order] SET StartDate = '" + newDate + "' WHERE Id = " + OrderId);
            ctx.ExecuteCommand("UPDATE BatchListe SET Status = 1 WHERE OrderId = " + OrderId + " AND BatchCountNo = 1");
        }

        /// <summary>
        /// Test method: Set Status = 0 for all batches in BatchListe
        /// </summary>
        /// <param name="LocationId"></param>
        public void ResetStatusBatches()
        {
            ctx = new DataHandlingDataContext(new Data().ConnectionString);

            ctx.ExecuteCommand("UPDATE BatchListe SET Status = 0");
        }


        #endregion


    }
}
